Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 55 additions & 14 deletions app/controllers/cameras/socs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,7 @@ def show
network_interface: 'eth',
sd_card_slot: 'nosd'
)
@camera.camera_ip_address = params[:cip] if params[:cip]
@camera.camera_mac_address = params[:mac].to_s.downcase.gsub('-', ':')
@camera.server_ip_address = params[:sip] if params[:sip]
@camera.flash_type = params[:rom] if params[:rom]
@camera.firmware_version = params[:ver] if params[:ver]
@camera.network_interface = params[:net] if params[:net]
@camera.sd_card_slot = params[:sd] if params[:sd]
apply_permalink_to(@camera)

# Soc.find, like every other action. find_by_urlname answers nil for a
# slug that does not exist, and the next line then raises NoMethodError on
Expand All @@ -73,13 +67,7 @@ def show
@camera.soc = Soc.find(params[:id])
@vendor = @camera.soc.vendor

# nor8m is the default for almost every SoC and wrong for the ones
# upstream builds only a NAND image for -- rv1109 and rv1126 here today.
# Their NOR sizes are disabled in the menu, so the form opened on a
# disabled flash type with no edition to go with it. Unrecognised is the
# same as unset: ?rom=nor64m otherwise opened the form on a chip that
# matches no option in it.
@camera.flash_type = @camera.soc.default_flash_chip unless @camera.flash_type.in?(Camera::FLASH_CHIP)
narrow_to_what_the_menu_offers(@camera)

@page_title = "SoC: #{@camera.soc.full_name}"
render 'cameras/socs/show'
Expand Down Expand Up @@ -236,6 +224,59 @@ def full_list

private

# Read a configuration back out of the query string Camera#permalink writes.
#
# The keys are the permanent link's, and they have to stay in step with it:
# `permalink` emitted `var` for as long as it existed against a `ver` that
# was never written, so the edition was the one field the link dropped and
# an Ultimate link reopened as Lite. `permalink` now writes `ver`; `var`
# stays readable here because every link anyone has shared carries it, and
# `ver` wins if a link somehow has both.
# One table rather than seven near-identical lines, because the drift was
# between a key and a field and a table is where that is visible. Insertion
# order is the precedence: `var` is applied first so `ver` overwrites it.
PERMALINK_FIELDS = { cip: :camera_ip_address, sip: :server_ip_address, rom: :flash_type,
var: :firmware_version, ver: :firmware_version,
net: :network_interface, sd: :sd_card_slot }.freeze
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.

def apply_permalink_to(camera)
# Not in the table: unlike the rest, the MAC is rewritten rather than
# copied, and it is applied whether or not the link carried one.
camera.camera_mac_address = params[:mac].to_s.downcase.gsub('-', ':')

PERMALINK_FIELDS.each do |key, field|
camera.public_send("#{field}=", params[key]) if params[key]
end
end

# Bring a configuration that arrived in the query string back inside what the
# menu on this page actually offers. Both rules below are the menu's; it
# applies them in JavaScript, after this action has already decided which
# options open selected.
#
# Silent, unlike the equivalents in `update`. There the choice decides what
# gets flashed and the visitor is told when it changes; here it only decides
# where a form opens, and they are about to press the button anyway.
def narrow_to_what_the_menu_offers(camera)
# nor8m is the default for almost every SoC and wrong for the ones upstream
# builds only a NAND image for -- rv1109 and rv1126 here today. Their NOR
# sizes are disabled in the menu, so the form opened on a disabled flash
# type with no edition to go with it. Unrecognised is the same as unset:
# ?rom=nor64m otherwise opened the form on a chip that matches no option.
camera.flash_type = camera.soc.default_flash_chip unless camera.flash_type.in?(Camera::FLASH_CHIP)

# Ultimate does not fit an 8MB chip. Reading `var` back made this
# reachable: ?rom=nor8m&var=ultimate opened the form on a combination
# enforce_eight_meg_limit refuses. Same carve-out as that method -- a SoC
# published as Ultimate and nothing else, hi3516cv6xx and hi3519dv500,
# keeps it, because naming a Lite tarball upstream never built is worse
# than the size warning `update` will give.
return unless camera.flash_type.eql?('nor8m') && camera.firmware_version.eql?('ultimate')
return unless camera.soc.available_releases('nor').include?('lite')

camera.firmware_version = 'lite'
end

# Which half is missing, in the visitor's terms.
#
# "This firmware does not exist" was the answer to all three, and for two
Expand Down
6 changes: 5 additions & 1 deletion app/models/camera.rb
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,11 @@ def permalink
'&sip=', server_ip_address,
'&net=', network_interface,
'&rom=', flash_type,
'&var=', firmware_version,
# `ver`, not `var`. This emitted `var` while show has always read `ver`,
# so the edition was the one field the permanent link dropped: reopening
# a link for Ultimate on a 32MB chip came back as Lite. show still
# accepts `var` too, because every link anyone has shared carries it.
'&ver=', firmware_version,
'&sd=', sd_card_slot
].join('').html_safe
end
Expand Down
86 changes: 86 additions & 0 deletions test/controllers/socs_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -550,4 +550,90 @@ def assert_told_to_remap_partitions(vendor_name)
test 'a 16MB Ingenic camera is told to run setnor16m after a full flash' do
assert_told_to_remap_partitions('Ingenic')
end

# --- the permanent link ---

def permalink_for(**attrs)
Camera.new(camera_mac_address: 'aa:bb:cc:dd:ee:ff', camera_ip_address: '10.0.0.5',
server_ip_address: '10.0.0.1', network_interface: 'eth',
sd_card_slot: 'nosd', **attrs).permalink
end

# Five of the seven fields, because five is what the form has. `net` and `sd`
# are set on the camera by the link and then rendered nowhere: the selects for
# them are commented out at show.html.erb:86-87, so the form posts neither and
# `update` falls back to its own eth/nosd defaults whatever the link said.
# Asserting them here would need `assigns`, which this app has no
# rails-controller-testing for -- and it would assert a value that goes no
# further than this request. That the link carries them at all is covered in
# camera_test.rb; that they survive to the next page is not true today.
def assert_form_reopened_on(chip, edition)
assert_response :success
assert_match 'value="aa:bb:cc:dd:ee:ff"', response.body
assert_match 'value="10.0.0.5"', response.body
assert_match 'value="10.0.0.1"', response.body
assert_match %(<option selected="selected" value="#{chip}">), response.body
assert_match %(<option selected="selected" value="#{edition}">), response.body
Comment thread
openipc-ai marked this conversation as resolved.
end

test 'a permanent link reopens the wizard on the configuration it names' do
soc = instructable_soc('TS3516EV800')

with_release_index(*every_edition_for(soc)) do
# net and sd carry values the form has no field for, so this also covers
# `show` accepting a link that names them rather than raising on one.
get "/cameras/vendors/#{soc.vendor.to_param}/socs/#{soc.to_param}" \
"#{permalink_for(flash_type: 'nor32m', firmware_version: 'ultimate',
network_interface: 'wifi', sd_card_slot: 'sd')}"

assert_form_reopened_on('nor32m', 'ultimate')
end
end

# permalink wrote `var` and this action read `ver`, so the edition was the one
# field that did not survive the round trip -- a link for Ultimate reopened as
# Lite. Every link anyone has shared was built by the old spelling, so `var`
# stays readable rather than being swapped out.
# The menu forbids Ultimate on an 8MB chip, but does it in JavaScript, after
# this action has chosen which option opens selected. Reading `var` back made
# that reachable: an old link for 8MB + Ultimate rendered the form on exactly
# the combination `update` refuses.
test 'an 8MB link does not open the form on an edition that does not fit' do
soc = instructable_soc('TS3516EVA00')

with_release_index(*every_edition_for(soc)) do
get "/cameras/vendors/#{soc.vendor.to_param}/socs/#{soc.to_param}" \
"#{permalink_for(flash_type: 'nor8m', firmware_version: 'ultimate')}"

assert_response :success
assert_match %(<option selected="selected" value="lite">), response.body
assert_no_match(/<option selected="selected" value="ultimate">/, response.body)
end
end

# The same carve-out enforce_eight_meg_limit makes. hi3516cv6xx and
# hi3519dv500 are published as Ultimate and nothing else, so forcing Lite
# would open the form on a build upstream has never produced.
test 'an 8MB link keeps Ultimate where it is the only edition published' do
soc = instructable_soc('TS3516EVB00')

with_release_index("openipc.#{soc.board}-nor-ultimate.tgz") do
get "/cameras/vendors/#{soc.vendor.to_param}/socs/#{soc.to_param}" \
"#{permalink_for(flash_type: 'nor8m', firmware_version: 'ultimate')}"

assert_response :success
assert_match %(<option selected="selected" value="ultimate">), response.body
end
end

test 'a link shared before the spelling was fixed still carries its edition' do
soc = instructable_soc('TS3516EV900')

with_release_index(*every_edition_for(soc)) do
get "/cameras/vendors/#{soc.vendor.to_param}/socs/#{soc.to_param}" \
'?mac=aa-bb-cc-dd-ee-ff&cip=10.0.0.5&sip=10.0.0.1&net=eth&rom=nor32m&var=ultimate&sd=nosd'

assert_form_reopened_on('nor32m', 'ultimate')
end
end
end
30 changes: 30 additions & 0 deletions test/models/camera_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,36 @@ def camera(flash_type:, firmware_version: 'ultimate')
assert_equal 'Zephyr', camera.firmware_version_name
end

# --- the permanent link ---

# permalink emitted `var` while Cameras::SocsController#show has only ever
# read `ver`, so the edition was the single field the link dropped: a link
# for Ultimate on a 32MB chip reopened as Lite. Nothing else diverged, which
# is why it went unnoticed -- so this pins the whole key set rather than the
# one key, against the reader in `show`.
test 'the permanent link is spelled with the keys show reads back' do
camera = Camera.new(camera_mac_address: 'aa:bb:cc:dd:ee:ff', camera_ip_address: '10.0.0.5',
server_ip_address: '10.0.0.1', network_interface: 'wifi',
flash_type: 'nor32m', firmware_version: 'ultimate', sd_card_slot: 'sd')

keys = Rack::Utils.parse_query(camera.permalink.delete_prefix('?')).keys

assert_equal %w[mac cip sip net rom ver sd].sort, keys.sort
end

test 'the permanent link carries every value it was built from' do
camera = Camera.new(camera_mac_address: 'aa:bb:cc:dd:ee:ff', camera_ip_address: '10.0.0.5',
server_ip_address: '10.0.0.1', network_interface: 'wifi',
flash_type: 'nor32m', firmware_version: 'ultimate', sd_card_slot: 'sd')

query = Rack::Utils.parse_query(camera.permalink.delete_prefix('?'))

# The MAC is the one field that changes shape: colons are not legal in a
# query string unescaped, and `show` turns the dashes back.
assert_equal({ 'mac' => 'aa-bb-cc-dd-ee-ff', 'cip' => '10.0.0.5', 'sip' => '10.0.0.1',
'net' => 'wifi', 'rom' => 'nor32m', 'ver' => 'ultimate', 'sd' => 'sd' }, query)
end

def with_index(assets)
root = Dir.mktmpdir
ENV['RELEASE_INDEX_ROOT'] = root
Expand Down
Loading