Skip to content

Commit

Permalink
Use a Struct to make easier dealing with services
Browse files Browse the repository at this point in the history
  • Loading branch information
dgdavid committed Mar 6, 2020
1 parent 6868909 commit b8ef658
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 74 deletions.
81 changes: 56 additions & 25 deletions library/network/src/modules/PortAliases.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ class PortAliasesClass < Module
}.freeze
private_constant :KNOWN_SERVICES

Service = Struct.new(:port, :aliases) do
def to_a
[port, aliases].flatten.map(&:to_s)
end
end

def main
textdomain "base"

Expand Down Expand Up @@ -115,21 +121,16 @@ def AllowedPortNameOrNumber
# @param [String] port-number or port-name
# @return [Array] [string] of aliases
def GetListOfServiceAliases(port)
service_aliases = [port]

# service is a port number
if Builtins.regexpmatch(port, "^[0123456789]+$")
port_number = port.to_i
service = find_by_port(port)

service_aliases << services[port_number]
return service.to_a if service
# service is a port name, any space isn't allowed
elsif IsAllowedPortName(port)
aliases = services.select { |_, v| v.include?(port) }
service = find_by_alias(port)

if aliases
service_aliases.unshift(aliases.keys.map(&:to_s))
service_aliases << aliases.values
end
return service.to_a if service
elsif !Builtins.contains(@cache_not_allowed_ports, port)
@cache_not_allowed_ports = Builtins.add(
@cache_not_allowed_ports,
Expand All @@ -140,7 +141,7 @@ def GetListOfServiceAliases(port)
Builtins.y2debug("Port name '%1' is not allowed", port)
end

service_aliases.compact.flatten.uniq
[port]
end

# Function returns if the requested port-name is known port.
Expand All @@ -162,11 +163,18 @@ def IsKnownPortName(port_name)
def GetPortNumber(port_name)
return Builtins.tointeger(port_name) if Builtins.regexpmatch(port_name, "^[0123456789]+$")

service = services.select { |_, v| v.include?(port_name) }

service.keys.first
service = services.find { |s| s.aliases.include?(port_name) }
service&.port
end

publish function: :IsAllowedPortName, type: "boolean (string)"
publish function: :AllowedPortNameOrNumber, type: "string ()"
publish function: :GetListOfServiceAliases, type: "list <string> (string)"
publish function: :IsKnownPortName, type: "boolean (string)"
publish function: :GetPortNumber, type: "integer (string)"

private

# Returns the collection of port => service aliases
#
# @see #load_services_database
Expand All @@ -175,25 +183,38 @@ def GetPortNumber(port_name)
def services
return @services if @services

@services = KNOWN_SERVICES.dup
# First, register known services
@services = KNOWN_SERVICES.map { |port, aliases| Service.new(port, aliases) }

# Then, process those returned by `getent servies`
load_services_database.each do |service|
# Each service line contains the name, port, and optionally aliases as follow
# service-name port service-aliases
name, port, aliases = service.chomp.split(" ")

key = port.to_i
aliases = [@services[key], name, aliases&.split].flatten.compact.sort.uniq
port = port.to_i
aliases = [name, aliases&.split].flatten.compact
service = find_by_port(port)

@services[key] = aliases
if service
service.aliases |= aliases
else
service = Service.new(port, aliases)
@services << service
end
end

@services
end

# Returns services database after performing some cleanup
#
# Basically, it discards duplicated lines after removing the protocol from the `getent` output.
# Basically, it manipulates the `getent services` output to discard duplicated lines after
# removing the protocol.
#
# Returned lines will look like
#
# EtherNet/IP-1 2222
# EtherNet-IP-2 44818
# rfb 5900 vnc-server
#
# @return [Array<String>]
def load_services_database
Expand All @@ -207,11 +228,21 @@ def load_services_database
).lines
end

publish function: :IsAllowedPortName, type: "boolean (string)"
publish function: :AllowedPortNameOrNumber, type: "string ()"
publish function: :GetListOfServiceAliases, type: "list <string> (string)"
publish function: :IsKnownPortName, type: "boolean (string)"
publish function: :GetPortNumber, type: "integer (string)"
# Convenience method to easily find a loaded service by its port number
#
# @param port [Integer, String] the port number
# @return [Service, nil]
def find_by_port(port_number)
services.find { |s| s.port == port_number.to_i }
end

# Convenience method to easily find a loaded service by alias
#
# @param port [Integer, String] the port number
# @return [Service, nil]
def find_by_alias(service_alias)
services.find { |s| s.aliases.include?(service_alias) }
end
end

PortAliases = PortAliasesClass.new
Expand Down
109 changes: 60 additions & 49 deletions library/network/test/port_aliases_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,50 @@

Yast.import "PortAliases"

# FILE CONTENT example
#
# blocks 10288/tcp # Blocks [Carl_Malamud]
# blocks 10288/udp # Blocks [Carl_Malamud]
# cosir 10321/tcp # Computer Op System Information Report [Kevin_C_Barber]
# # 10321/udp Reserved
# bngsync 10439/udp # BalanceNG session table synchronization protocol [Inlab_Software_GmbH] [Thomas_G._Obermair]
# # 10439/tcp Reserved
# # 10500/tcp Reserved
# hip-nat-t 10500/udp # HIP NAT-Traversal [RFC5770] [Ari_Keranen]
# MOS-lower 10540/tcp # MOS Media Object Metadata Port [Eric_Thorniley]
# MOS-lower 10540/udp # MOS Media Object Metadata Port [Eric_Thorniley]
# MOS-upper 10541/tcp # MOS Running Order Port [Eric_Thorniley]
# MOS-upper 10541/udp # MOS Running Order Port [Eric_Thorniley]

GETENT_OUTPUT = <<~GETENT_OUTPUT.freeze
blocks 10288
blocks 10288
cosir 10321
bngsync 10439
hip-nat-t 10500
MOS-lower 10540
MOS-lower 10540
MOS-upper 10541
MOS-upper 10541
GETENT_OUTPUT

describe Yast::PortAliases do
let(:executor) { instance_double(Yast::Execute, on_target!: GETENT_OUTPUT) }
# /usr/etc/servcies content example
#
# EtherNet-IP-1 2222/tcp # EtherNet/IP I/O ;; IANA assigned this well-formed service name as a replacement for "EtherNet/IP-1". [Brian_Batke_2]
# EtherNet/IP-1 2222/tcp # EtherNet/IP I/O [Brian_Batke_2]
# EtherNet-IP-1 2222/udp # EtherNet/IP I/O ;; IANA assigned this well-formed service name as a replacement for "EtherNet/IP-1". [Brian_Batke_2]
# EtherNet/IP-1 2222/udp # EtherNet/IP I/O [Brian_Batke_2]
# rfb 5900/tcp vnc-server # Remote Framebuffer [Tristan_Richardson] [RFC6143]
# rfb 5900/udp vnc-server # Remote Framebuffer [Tristan_Richardson] [RFC6143]
# blocks 10288/tcp # Blocks [Carl_Malamud]
# blocks 10288/udp # Blocks [Carl_Malamud]
# cosir 10321/tcp # Computer Op System Information Report [Kevin_C_Barber]
# # 10321/udp Reserved
# bngsync 10439/udp # BalanceNG session table synchronization protocol [Inlab_Software_GmbH] [Thomas_G._Obermair]
# # 10439/tcp Reserved
# # 10500/tcp Reserved
# hip-nat-t 10500/udp # HIP NAT-Traversal [RFC5770] [Ari_Keranen]
# MOS-lower 10540/tcp # MOS Media Object Metadata Port [Eric_Thorniley]
# MOS-lower 10540/udp # MOS Media Object Metadata Port [Eric_Thorniley]
# MOS-upper 10541/tcp # MOS Running Order Port [Eric_Thorniley]
# MOS-upper 10541/udp # MOS Running Order Port [Eric_Thorniley]
# EtherNet-IP-2 44818/tcp # EtherNet/IP messaging ;; IANA assigned this well-formed service name as a replacement for "EtherNet/IP-2". [Brian_Batke_2]
# EtherNet/IP-2 44818/tcp # EtherNet/IP messaging [Brian_Batke_2]
# EtherNet-IP-2 44818/udp # EtherNet/IP messaging ;; IANA assigned this well-formed service name as a replacement for "EtherNet/IP-2". [Brian_Batke_2]
# EtherNet/IP-2 44818/udp # EtherNet/IP messaging [Brian_Batke_2]

# The output generated by Yast::Execute after process the file example above
let(:output) do
<<~OUTPUT
EtherNet-IP-1 2222
EtherNet/IP-1 2222
rfb 5900 vnc-server
blocks 10288
cosir 10321
bngsync 10439
hip-nat-t 10500
MOS-lower 10540
MOS-upper 10541
EtherNet-IP-2 44818
EtherNet/IP-2 44818
OUTPUT
end

let(:executor) { instance_double(Yast::Execute, on_target!: output) }

before do
allow(Yast::Execute).to receive(:stdout).and_return(executor)
Expand Down Expand Up @@ -75,13 +90,13 @@
context "when a name is given" do
context "containing only valid chars" do
it "returns true" do
expect(subject.IsAllowedPortName("valid-service.name+")).to eq(true)
expect(subject.IsAllowedPortName("valid-service.name-or-alias")).to eq(true)
end
end

context "containing not valid chars" do
it "returns false" do
expect(subject.IsAllowedPortName("Not valid service name")).to eq(false)
expect(subject.IsAllowedPortName("Not valid service alias")).to eq(false)
end
end
end
Expand All @@ -104,15 +119,15 @@
context "and there is a service for such port number" do
let(:port_number) { "10541" }

it "returns a list holding both, the port number and its aliases" do
it "returns a list holding both, the port number and aliases" do
expect(subject.GetListOfServiceAliases(port_number)).to eq(["10541", "MOS-upper"])
end
end

context "but there is not a service for such port number" do
let(:port_number) { "666" }

it "returns a list holding only the given port number" do
it "returns a list holding only given argument" do
expect(subject.GetListOfServiceAliases(port_number)).to eq([port_number])
end
end
Expand All @@ -131,7 +146,7 @@
context "but there is not a service for such port number" do
let(:port_name) { "SomethingWrong" }

it "returns a list holding only the given name" do
it "returns a list holding only given argument" do
expect(subject.GetListOfServiceAliases(port_name)).to eq([port_name])
end
end
Expand All @@ -145,7 +160,7 @@
subject.GetListOfServiceAliases(port_name)
end

it "returns a list holding only the given name" do
it "returns a list holding only given argument" do
expect(subject.GetListOfServiceAliases(port_name)).to eq([port_name])
end
end
Expand All @@ -155,45 +170,41 @@

describe ".IsKnownPortName" do
context "when a known port name is given" do
let(:port_name) { "blocks" }
it "returns true" do
expect(subject.IsKnownPortName("blocks")).to eq(true)
end
end

context "when a known port alias is given" do
it "returns true" do
expect(subject.IsKnownPortName(port_name)).to eq(true)
expect(subject.IsKnownPortName("vnc-server")).to eq(true)
end
end

context "when an unknown port name is given" do
let(:port_name) { "unknown-port" }

it "returns false" do
expect(subject.IsKnownPortName(port_name)).to eq(false)
expect(subject.IsKnownPortName("unknown-name-or-alias")).to eq(false)
end
end
end

describe ".GetPortNumber" do
context "when a port number is given" do
let(:port_number) { "80" }

it "returns the Integer port number" do
expect(subject.GetPortNumber(port_number)).to eq(80)
it "returns the port number as an Integer" do
expect(subject.GetPortNumber("80")).to eq(80)
end
end

context "when a port name is given" do
context "and the port is known" do
let(:port_name) { "MOS-lower" }

it "returns its port number" do
expect(subject.GetPortNumber(port_name)).to eq(10540)
expect(subject.GetPortNumber("vnc-server")).to eq(5900)
end
end

context "but the port is unknown" do
let(:port_name) { "Unknown" }

it "returns nil" do
expect(subject.GetPortNumber(port_name)).to be_nil
expect(subject.GetPortNumber("Unknown-service")).to be_nil
end
end
end
Expand Down

0 comments on commit b8ef658

Please sign in to comment.